
#pragma once
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm/util.hpp"
#include "comm/log.hpp"

namespace ns_runner
{

    using namespace ns_log;
    using namespace ns_util;

    class Runner
    {

    public:
        Runner() {}
        ~Runner() {}

        /*

            @attention
            为什么返回值要设置为 int：
            如果返回值 > 0，程序异常，退出收到了信号，返回值就是对应的信号
            返回值 == 0， 正常运行完毕，结果保存在对应的临时文件中

         @note
            ```
            程序运行：
            1. 代码跑完的结果只有正确和不正确两种结果
            2.Run不需要老吕运行的结果是否正确运行是否正确是由测试用例决定的！
            我们必须要考虑可执行程序是谁？
            * 标准输入：不处理
            * 标准输出：程序运行完成，输出的结果是什么
            * 标准错误：运行时错误信息
            ```
            @param cpuLimits 该程序运行时，可以使用的最大的 cpu资源
            @param memLimits 该程序在运行的时候。可以使用的最大的内存大小(kb)
        */
        static int runner(const std::string &fileName,
                          int cpuLimit, int memLimit)
        {

            std::string _execute = PathUtil::Exe(fileName),
                        _stdin = PathUtil::Stdin(fileName),
                        _stdout = PathUtil::Stdout(fileName),
                        _stderr = PathUtil::Stderr(fileName);
            umask(0);
            int _stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDWR, 0644);
            int _stdout_fd = open(_stdout.c_str(), O_CREAT | O_RDWR, 0644);
            int _stderr_fd = open(_stderr.c_str(), O_CREAT | O_RDWR, 0644);

            if (_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
            {
                LOG(ERROR) << "运行时标准文件打开失败" << std::endl;
                return -1;
            }
            auto pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "运行时创建子进程失败" << std::endl;
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                LOG(ERROR) << "创建子进程失败" << std::endl;
                return -2;
            }
            else if (pid == 0)
            {
                dup2(_stdin_fd, 0);
                dup2(_stdout_fd, 1);
                dup2(_stderr_fd, 2);
                LOG(INFO) << "runner execute here" << std::endl;
                setProcLimit(cpuLimit, memLimit); // 映射出错 map segment failed    那可能跟memLimit参数有关
                // 用的是相对路径
                if (execl(_execute.c_str() /*我要执行谁*/, _execute.c_str() /*我要怎么执行*/, NULL) < 0)
                {
                    perror("execl failed: ");
                }
                exit(1);
            }
            else
            {
                close(_stdin_fd);
                close(_stdout_fd);
                close(_stderr_fd);
                int status = 0;
                ::waitpid(pid, &status, 0);
                //* 程序运行异常一定收到了信号
                LOG(INFO) << "运行完毕, info : " << (status & 0x7f) << std::endl;
                return status & 0x7f;
            }
        }

    private:
        /*
            @brief 提供设置进程资源占用大小的接口
            @note
            ```
            设置cpu占用时长
            设置内存占用大小, 单位是 kb
            ```
        */
        static void setProcLimit(int &cpuLimits, int &memLimits)
        {
            // * 1. 设置 cpu时长
            struct rlimit cpu_rlimit;
            cpu_rlimit.rlim_max = RLIM_INFINITY;
            cpu_rlimit.rlim_cur = cpuLimits;
            setrlimit(RLIMIT_CPU, &cpu_rlimit);

            // * 2. 设置内存大小
            struct rlimit mem_rlimit;
            mem_rlimit.rlim_max = RLIM_INFINITY;
            mem_rlimit.rlim_cur = memLimits * 1024; // 单位是 kb    4*1024*1024
            setrlimit(RLIMIT_AS, &mem_rlimit);
        }
    };
}